#ifndef SL_FRUSTUM_H_
#define SL_FRUSTUM_H_
#include "Config.h"
#include "Referenced.h"
#include "Plane.h"
#include "Vector3.h"
#include "Matrix4.h"
#include "BoundingBox.h"
#include "BoundingSphere.h"

namespace sl
{
	class SL_EXPORT Frustum : public Referenced
	{
	public:

		enum 
		{
			PLANE_LEFT = 0,
			PLANE_RIGHT,
			PLANE_TOP,
			PLANE_BOTTOM,
			PLANE_NEAR,
			PLANE_FAR,
			PLANE_NUM
		};

		Frustum(void);
		~Frustum(void);

		void SetDefaultFrustum()
		{
			//left right top bottom
			m_vPlane[PLANE_LEFT]   = Plane( 1.0, 0.0, 0.0, 1.0 );
			m_vPlane[PLANE_RIGHT]  = Plane( -1.0, 0.0, 0.0, 1.0 );
			m_vPlane[PLANE_TOP]    = Plane( 0.0, 1.0, 0.0, 1.0 );
			m_vPlane[PLANE_BOTTOM] = Plane( 0.0, -1.0, 0.0, 1.0 );
			// near and far
			m_vPlane[PLANE_NEAR]   = Plane( 0.0, 0.0, 1.0, 1.0 );
			m_vPlane[PLANE_FAR]    = Plane( 0.0, 0.0, -1.0, 1.0 );
		}

		void GenerateFrustumFromMatrix( sl::Matrix4 & traslation )
		{
			m_vPlane[PLANE_LEFT][0] = traslation[3][0] + traslation[0][0];
			m_vPlane[PLANE_LEFT][1] = traslation[3][1] + traslation[0][1];
			m_vPlane[PLANE_LEFT][2] = traslation[3][2] + traslation[0][2];
			m_vPlane[PLANE_LEFT][3] = traslation[3][3] + traslation[0][3];
			m_vPlane[PLANE_LEFT].MakeNormalise();

			m_vPlane[PLANE_RIGHT][0] = traslation[3][0] - traslation[0][0];
			m_vPlane[PLANE_RIGHT][1] = traslation[3][1] - traslation[0][1];
			m_vPlane[PLANE_RIGHT][2] = traslation[3][2] - traslation[0][2];
			m_vPlane[PLANE_RIGHT][3] = traslation[3][3] - traslation[0][3];
			m_vPlane[PLANE_RIGHT].MakeNormalise();

			m_vPlane[PLANE_TOP][0]  = traslation[3][0] - traslation[1][0];
			m_vPlane[PLANE_TOP][1]  = traslation[3][1] - traslation[1][1];
			m_vPlane[PLANE_TOP][2]  = traslation[3][2] - traslation[1][2];
			m_vPlane[PLANE_TOP][3]  = traslation[3][3] - traslation[1][3];
			m_vPlane[PLANE_TOP].MakeNormalise();

			m_vPlane[PLANE_BOTTOM][0] = traslation[3][0] + traslation[1][0];
			m_vPlane[PLANE_BOTTOM][1] = traslation[3][1] + traslation[1][1];
			m_vPlane[PLANE_BOTTOM][2] = traslation[3][2] + traslation[1][2];
			m_vPlane[PLANE_BOTTOM][3] = traslation[3][3] + traslation[1][3];
			m_vPlane[PLANE_BOTTOM].MakeNormalise();

			m_vPlane[PLANE_NEAR][0] = traslation[3][0] + traslation[2][0];
			m_vPlane[PLANE_NEAR][1] = traslation[3][1] + traslation[2][1];
			m_vPlane[PLANE_NEAR][2] = traslation[3][2] + traslation[2][2];
			m_vPlane[PLANE_NEAR][3] = traslation[3][3] + traslation[2][3];
			m_vPlane[PLANE_NEAR].MakeNormalise();

			m_vPlane[PLANE_FAR][0] = traslation[3][0] - traslation[2][0];
			m_vPlane[PLANE_FAR][1] = traslation[3][1] - traslation[2][1];
			m_vPlane[PLANE_FAR][2] = traslation[3][2] - traslation[2][2];
			m_vPlane[PLANE_FAR][3] = traslation[3][3] - traslation[2][3];
			m_vPlane[PLANE_FAR].MakeNormalise();

		}

		// base on the project and view to set the right value
		void GenerateFrustumWithWiewAndPorject( sl::Matrix4 project,sl::Matrix4 view )
		{
			sl::Matrix4 combo = project * view;
			GenerateFrustumFromMatrix( combo );
		}

		// point
		bool IsContains( const sl::Vector3 & vec )
		{
			for ( u32 i = PLANE_LEFT; i < PLANE_NUM; ++i )
			{
				// in the negative side
				if ( m_vPlane[i].GetSide(vec) == Plane::SIDE_NEGATIVE  )
					return false;
			}
			return true;
		}

		// box
		bool IsContains( sl::BoundingBox * box )
		{
			for ( u32 i = PLANE_LEFT; i < PLANE_NUM; ++i )
			{
				// 
				if ( m_vPlane[i].GetSide( *box ) == Plane::SIDE_NEGATIVE)
					return false;
			}
			return true;
		}

		// sphere
		bool IsContains( sl::BoundingSphere * sphere )
		{
			for ( u32 i = PLANE_LEFT; i < PLANE_NUM; ++i )
			{
				// why the radius is negative
				if ( m_vPlane[i].GetSide( *sphere ) == Plane::SIDE_NEGATIVE )
					return false;
			}
			return true;
		}
	private:
		Plane		m_vPlane[PLANE_NUM];	
	};
}

#endif

